using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;

namespace SeleniumDocs.ActionsAPI
{
    [TestClass]
    public class PenTest : BaseChromeTest
    {
        [TestMethod]
        public void UsePen()
        {
            driver.Url = "https://selenium.dev/selenium/web/pointerActionsPage.html";

            IWebElement pointerArea = driver.FindElement(By.Id("pointerArea"));
            ActionBuilder actionBuilder = new ActionBuilder();
            PointerInputDevice pen = new PointerInputDevice(PointerKind.Pen, "default pen");
            
            actionBuilder.AddAction(pen.CreatePointerMove(pointerArea, 0, 0, TimeSpan.FromMilliseconds(800)));
            actionBuilder.AddAction(pen.CreatePointerDown(MouseButton.Left));
            actionBuilder.AddAction(pen.CreatePointerMove(CoordinateOrigin.Pointer,
                2, 2, TimeSpan.Zero));
            actionBuilder.AddAction(pen.CreatePointerUp(MouseButton.Left));
            ((IActionExecutor)driver).PerformActions(actionBuilder.ToActionSequenceList());

            var moves = driver.FindElements(By.ClassName("pointermove"));
            var moveTo = getProperties(moves.ElementAt(0));
            var down = getProperties(driver.FindElement(By.ClassName("pointerdown")));
            var moveBy = getProperties(moves.ElementAt(1));
            var up = getProperties(driver.FindElement(By.ClassName("pointerup")));

            Point location = pointerArea.Location;
            Size size = pointerArea.Size;
            decimal centerX = location.X + size.Width / 2;
            decimal centerY = location.Y + size.Height / 2;

            Assert.AreEqual("-1", moveTo["button"]);
            Assert.AreEqual("pen", moveTo["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX, moveTo["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY, moveTo["pageY"]));
            Assert.AreEqual("0", down["button"]);
            Assert.AreEqual("pen", down["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX, down["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY, down["pageY"]));
            Assert.AreEqual("-1", moveBy["button"]);
            Assert.AreEqual("pen", moveBy["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX + 2, moveBy["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY + 2, moveBy["pageY"]));
            Assert.AreEqual("0", up["button"]);
            Assert.AreEqual("pen", up["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX + 2, up["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY + 2, up["pageY"]));
        }

        [TestMethod]
        public void SetPointerEventProperties()
        {
            driver.Url = "https://selenium.dev/selenium/web/pointerActionsPage.html";

            IWebElement pointerArea = driver.FindElement(By.Id("pointerArea"));
            ActionBuilder actionBuilder = new ActionBuilder();
            PointerInputDevice pen = new PointerInputDevice(PointerKind.Pen, "default pen");
            PointerInputDevice.PointerEventProperties properties = new PointerInputDevice.PointerEventProperties() {
                TiltX = -72,
                TiltY = 9,
                Twist = 86,
            };            
            actionBuilder.AddAction(pen.CreatePointerMove(pointerArea, 0, 0, TimeSpan.FromMilliseconds(800)));
            actionBuilder.AddAction(pen.CreatePointerDown(MouseButton.Left));
            actionBuilder.AddAction(pen.CreatePointerMove(CoordinateOrigin.Pointer,
                2, 2, TimeSpan.Zero, properties));
            actionBuilder.AddAction(pen.CreatePointerUp(MouseButton.Left));
            ((IActionExecutor)driver).PerformActions(actionBuilder.ToActionSequenceList());
            
            var moves = driver.FindElements(By.ClassName("pointermove"));
            var moveTo = getProperties(moves.ElementAt(0));
            var down = getProperties(driver.FindElement(By.ClassName("pointerdown")));
            var moveBy = getProperties(moves.ElementAt(1));
            var up = getProperties(driver.FindElement(By.ClassName("pointerup")));

            Point location = pointerArea.Location;
            Size size = pointerArea.Size;
            decimal centerX = location.X + size.Width / 2;
            decimal centerY = location.Y + size.Height / 2;

            Assert.AreEqual("-1", moveTo["button"]);
            Assert.AreEqual("pen", moveTo["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX, moveTo["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY, moveTo["pageY"]));
            Assert.AreEqual("0", down["button"]);
            Assert.AreEqual("pen", down["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX, down["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY, down["pageY"]));
            Assert.AreEqual("-1", moveBy["button"]);
            Assert.AreEqual("pen", moveBy["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX + 2, moveBy["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY + 2, moveBy["pageY"]));
            Assert.AreEqual((-72).ToString(), moveBy["tiltX"]);
            Assert.AreEqual((9).ToString(), moveBy["tiltY"]);
            Assert.AreEqual((86).ToString(), moveBy["twist"]);
            Assert.AreEqual("0", up["button"]);
            Assert.AreEqual("pen", up["pointerType"]);
            Assert.IsTrue(VerifyEquivalent(centerX + 2, up["pageX"]));
            Assert.IsTrue(VerifyEquivalent(centerY + 2, up["pageY"]));
        }

        private Dictionary<string, string> getProperties(IWebElement element)
        {
            var str = element.Text;
            str = str[(str.Split()[0].Length + 1)..];
            IEnumerable<string[]> keyValue = str.Split(", ").Select(part => part.Split(":"));
            return keyValue.ToDictionary(split => split[0].Trim(), split => split[1].Trim());
        }

        private bool VerifyEquivalent(decimal expected, string actual)
        {
            var absolute = Math.Abs(expected - decimal.Parse(actual));
            if (absolute <= 1)
            {
                return true;
            }

            throw new Exception("Expected: " + expected + "; but received: " + actual);
        }
    }
}